#include <crt_util.h>

#if (TARGET_OS == OS_WIN)

int _cond_init(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    _sem_init(&cond->wait_sem, 0);
    _sem_init(&cond->wait_done, 0);
    _mutex_init(&cond->lock);

    cond->nsignal = 0;
    cond->waiting = 0;

    return S_SUCCESS;
}

int _cond_destroy(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    _sem_destroy(&cond->wait_sem);
    _sem_destroy(&cond->wait_done);
    _mutex_destroy(&cond->lock);

    cond->nsignal = 0;
    cond->waiting = 0;

    return S_SUCCESS;
}

int _cond_signal(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    _mutex_lock(&cond->lock);

    if (cond->waiting > cond->nsignal) {
        cond->nsignal++;
        ;

        _sem_post(&cond->wait_sem);
        _mutex_unlock(&cond->lock);
        _sem_wait(&cond->wait_done);
    } else {
        _mutex_unlock(&cond->lock);
    }

    return S_SUCCESS;
}

int _cond_broadcast(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    _mutex_lock(&cond->lock);
    if (cond->waiting > cond->nsignal) {
        int i = 0;
        int num = 0;

        num = (cond->waiting - cond->nsignal);

        cond->nsignal = cond->waiting;

        for (i = 0; i < num; ++i)
            _sem_post(&cond->wait_sem);

        _mutex_unlock(&cond->lock);

        for (i = 0; i < num; ++i)
            _sem_wait(&cond->wait_done);
    } else {
        _mutex_unlock(&cond->lock);
    }

    return S_SUCCESS;
}

int _cond_wait(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    return _cond_timedwait(cond, INFINITE);
}

int _cond_timedwait(_cond_t* cond, unsigned long ms)
{
    rc_error(cond != NULL, S_ERROR);
    unsigned long result = 0;
    int last_waiter;

    HANDLE handles[2] = {cond->wait_sem, cond->wait_done};

    _mutex_unlock(&cond->lock);

    {
        _mutex_lock(&cond->lock);
        cond->waiting++;
        _mutex_unlock(&cond->lock);

        result = WaitForMultipleObjects(2, handles, FALSE, ms);

        _mutex_lock(&cond->lock);
        cond->waiting--;
        _mutex_unlock(&cond->lock);

        last_waiter = (result == WAIT_OBJECT_0 + 1) && (cond->waiting == 0);

        if (cond->nsignal > 0) {
            _sem_post(&cond->wait_done);

            _mutex_lock(&cond->lock);
            cond->nsignal--;
            _mutex_unlock(&cond->lock);
        }
    }

    _mutex_lock(&cond->lock);

    if (result == WAIT_OBJECT_0 || result == WAIT_OBJECT_0 + 1)
        return S_SUCCESS;

    if (result == WAIT_TIMEOUT)
        return S_TIMEOUT;

    return S_ERROR;
}

#elif (TARGET_OS == OS_POSIX)

int _cond_init(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (pthread_mutex_init(&cond->mutex, NULL)) {
        return S_ERROR;
    }

    if (cond->relative) {
        int ret = 0;
        ret = pthread_condattr_init(&cond->attr);
        ret = pthread_condattr_setclock(&cond->attr, CLOCK_MONOTONIC);

        if (pthread_cond_init(&cond->c, &cond->attr)) {
            return S_ERROR;
        }
    } else {
        if (pthread_cond_init(&cond->c, NULL)) {
            return S_ERROR;
        }
    }
    return S_SUCCESS;
}

int _cond_destroy(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (cond->relative) {
        pthread_condattr_destroy(&(cond->attr));
    }

    if (pthread_mutex_destroy(&cond->mutex)) { }

    if (pthread_cond_destroy(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_signal(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_signal(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_broadcast(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_broadcast(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_wait(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    pthread_mutex_lock(&cond->mutex);

    if (pthread_cond_wait(&cond->c, &cond->mutex)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_timedwait(_cond_t* cond, unsigned long ms)
{
    rc_error(cond != NULL, S_ERROR);
    int rc;
    int retval;

    struct timeval delta;
    struct timespec abstime;

    if (cond->relative) {
        clock_gettime(CLOCK_MONOTONIC, &abstime);
        abstime.tv_nsec += (ms % 1000) * 1000000;
        abstime.tv_sec += ms / 1000;
    } else {
        gettimeofday(&delta, NULL);
        abstime.tv_sec = delta.tv_sec + (ms / 1000);
        abstime.tv_nsec = (delta.tv_usec + (ms % 1000) * 1000) * 1000;
    }

    if (abstime.tv_nsec > 1000000000) {
        abstime.tv_sec += 1;
        abstime.tv_nsec -= 1000000000;
    }

    rc = pthread_mutex_lock(&cond->mutex);

#if defined(__ANDROID_API__) && __ANDROID_API__ < 21
    rc = pthread_cond_timedwait_monotonic_np(&cond->c, &cond->mutex, &abstime);
#else
    rc = pthread_cond_timedwait(&cond->c, &cond->mutex, &abstime);
#endif

    if (rc == ETIMEDOUT)
        return S_TIMEOUT;

    if (rc) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

#elif (TARGET_OS == OS_MACH)

int _cond_init(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (pthread_mutex_init(&cond->mutex, NULL)) {
        return S_ERROR;
    }

    if (pthread_cond_init(&cond->c, NULL)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _cond_destroy(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (pthread_mutex_destroy(&cond->mutex)) { }

    if (pthread_cond_destroy(&cond->c)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _cond_signal(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_signal(&cond->c)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _cond_broadcast(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_broadcast(&cond->c)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _cond_wait(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    pthread_mutex_lock(&cond->mutex);

    if (pthread_cond_wait(&cond->c, &cond->mutex)) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

int _cond_timedwait(_cond_t* cond, unsigned long ms)
{
    rc_error(cond != NULL, S_ERROR);
    int rc;

    struct timespec abstime;

    if (__builtin_available(macOS 10.12, *)) {
        if (__builtin_available(iOS 10.0, *)) {
            clock_gettime(_CLOCK_MONOTONIC, &abstime);
        } else {
            // Fallback on earlier versions
        }
        if (__builtin_available(iOS 10.0, *)) {
            clock_gettime(_CLOCK_MONOTONIC, &abstime);
        } else {
            // Fallback on earlier versions
        }
    } else {
        // Fallback on earlier versions
    }
    abstime.tv_nsec += (ms % 1000) * 1000000;
    abstime.tv_sec += ms / 1000;

    if (abstime.tv_nsec > 1000000000) {
        abstime.tv_sec += 1;
        abstime.tv_nsec -= 1000000000;
    }

    rc = pthread_mutex_lock(&cond->mutex);

    rc = pthread_cond_timedwait_relative_np(&cond->c, &cond->mutex, &abstime);

    if (rc == ETIMEDOUT)
        return S_TIMEOUT;

    if (rc) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

#elif (TARGET_OS == OS_UNIX)

int _cond_init(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (pthread_mutex_init(&cond->mutex, NULL)) {
        return S_ERROR;
    }

    if (cond->relative) {
        int ret = 0;
        ret = pthread_condattr_init(&cond->attr);

        ret = pthread_condattr_setclock(&cond->attr, CLOCK_MONOTONIC);

        if (pthread_cond_init(&cond->c, &cond->attr)) {
            return S_ERROR;
        }
    } else {
        if (pthread_cond_init(&cond->c, NULL)) {
            return S_ERROR;
        }
    }
    return S_SUCCESS;
}

int _cond_destroy(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    if (cond->relative) {
        pthread_condattr_destroy(&(cond->attr));
    }

    if (pthread_mutex_destroy(&cond->mutex)) { }

    if (pthread_cond_destroy(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_signal(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_signal(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_broadcast(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);
    if (pthread_cond_broadcast(&cond->c)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

/*
https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/lib/librthread/Attic/rthread_cond.c?rev=1.3&content-type=text/plain
*/

int _cond_wait(_cond_t* cond)
{
    rc_error(cond != NULL, S_ERROR);

    pthread_mutex_lock(&cond->mutex);

    if (pthread_cond_wait(&cond->c, &cond->mutex)) {
        return S_ERROR;
    }
    return S_SUCCESS;
}

int _cond_timedwait(_cond_t* cond, unsigned long ms)
{
    rc_error(cond != NULL, S_ERROR);
    int rc;
    int retval;

    struct timeval delta;
    struct timespec abstime;

    if (cond->relative) {
        clock_gettime(CLOCK_MONOTONIC, &abstime);
        abstime.tv_nsec += (ms % 1000) * 1000000;
        abstime.tv_sec += ms / 1000;
    } else {
        gettimeofday(&delta, NULL);
        abstime.tv_sec = delta.tv_sec + (ms / 1000);
        abstime.tv_nsec = (delta.tv_usec + (ms % 1000) * 1000) * 1000;
    }

    if (abstime.tv_nsec > 1000000000) {
        abstime.tv_sec += 1;
        abstime.tv_nsec -= 1000000000;
    }

	
    rc = pthread_mutex_lock(&cond->mutex);

    rc = pthread_cond_timedwait(&cond->c, &cond->mutex, &abstime);

    if (rc == ETIMEDOUT)
        return S_TIMEOUT;

    if (rc) {
        return S_ERROR;
    }

    return S_SUCCESS;
}

#endif
